// 使用 list 剔除空闲连接，头部存放即将过期的连接，新的连接或者有消息到达时都把连接更新到尾部。
#include <muduo/base/Logging.h>
#include <muduo/base/copyable.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpServer.h>
#include <boost/bind.hpp>
#include <boost/any.hpp>
#include <list>
#include <stdio.h>

using namespace muduo;
using namespace muduo::net;

// RFC 862
class EchoServer {
public:
	EchoServer(EventLoop *loop, const InetAddress &listenAddr, int idleSeconds)
		: server_(loop, listenAddr, "EchoServer"),
		  idldSeconds_(idleSeconds) {
		server_.setConnectionCallback(boost::bind(&EchoServer::OnConnection, this, _1));
		server_.setMessageCallback(boost::bind(&EchoServer::OnMessage, this, _1, _2, _3));
		loop->runEvery(1.0, boost::bind(&EchoServer::OnTimer, this));
		DumpConnectionList();
	}

	void Start() {
		server_.start();
	}

private:
	typedef boost::weak_ptr<TcpConnection> WeakTcpConnectionPtr;
	typedef std::list<WeakTcpConnectionPtr> WeakConnectionList;

	void OnConnection(const TcpConnectionPtr &conn) {
		LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
	         << conn->localAddress().toIpPort() << " is "
	         << (conn->connected() ? "UP" : "DOWN");
	    if (conn->connected()) {
	    	Node node;
	    	node.lastReceiveTime = Timestamp::now();
	    	connectionList_.push_back(conn);
	    	node.position = --connectionList_.end(); // 指向最后一位元素
	    	conn->setContext(node);
	    } else {
	    	assert(!conn->getContext().empty());
	    	const Node &node = boost::any_cast<const Node&>(conn->getContext());
	    	connectionList_.erase(node.position);
	    }
	}

	void OnMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp time) {
		muduo::string msg(buf->retrieveAllAsString());
		LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "
		         << "data received at " << time.toString();
		conn->send(msg);

		assert(!conn->getContext().empty());
		Node *node = boost::any_cast<Node>(conn->getMutableContext());
		node->lastReceiveTime = time;
		connectionList_.splice(connectionList_.end(), connectionList_, node->position);
		assert(node->position == --connectionList_.end());

		DumpConnectionList();
	}

	void OnTimer() {
		DumpConnectionList();

		Timestamp now = Timestamp::now();
		for (WeakConnectionList::iterator it = connectionList_.begin();
			 it != connectionList_.end();) {
			TcpConnectionPtr conn = it->lock();
			if (conn) {
				Node *n = boost::any_cast<Node>(conn->getMutableContext());
				double age = timeDifference(now, n->lastReceiveTime);
				if (age > idldSeconds_) {
					if (conn->connected()) {
						conn->shutdown();
						LOG_INFO << "shutting down " << conn->name();
						conn->forceCloseWithDelay(3.5);  // > round trip of the whole Internet.
					}
				} else if (age < 0) {
					LOG_WARN << "time jump";
					n->lastReceiveTime = now;
				} else {
					break;
				}
				++it;
			} else {
				LOG_WARN << "expired";
				connectionList_.erase(it);
			}
		}
	}

	void DumpConnectionList() const {
		Timestamp now = Timestamp::now();
		LOG_INFO << "*** size = " << connectionList_.size();
		for (WeakConnectionList::const_iterator it = connectionList_.begin();
			 it != connectionList_.end();
			 ++it) {
			TcpConnectionPtr conn = it->lock();
			if (conn) {
				printf("conn %p", boost::get_pointer(conn));
				const Node &n = boost::any_cast<const Node&>(conn->getContext());
				printf("      second %f\n", timeDifference(now, n.lastReceiveTime));
			} else {
				printf("expired\n");
			}
		}
	}

	struct Node : public muduo::copyable {
		Timestamp lastReceiveTime;
		WeakConnectionList::iterator position;
	};

	TcpServer server_;
	int idldSeconds_;
	WeakConnectionList connectionList_; // 不能用 vector 因为添加元素会重新分配内存，用了 vector 之前的迭代器可能失效。
};

int 
main(int argc, char **argv) {
	Logger::setLogLevel(Logger::INFO);
	EventLoop loop;
	InetAddress listenAddr(2007);
	int idleSeconds = 10;
	if (argc > 1) {
		idleSeconds = atoi(argv[1]);
	}
	LOG_INFO << "pid = " << getpid() << ", idle seconds = " << idleSeconds;
	EchoServer server(&loop, listenAddr, idleSeconds);
	server.Start();
	loop.loop();
	return 0;
}
